﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Threading;
using System.Windows.Documents;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

namespace Kokomo.PeInspector.Reflection
{
	class FlowDocumentBuilder
	{
		public Dispatcher Dispatcher { get; private set; }

		public FlowDocumentBuilder(
			FlowDocument document
			)
		{
			if (document == null) throw new ArgumentNullException("document");

			this.Document = document;
			this.Dispatcher = document.Dispatcher;

			this.Dispatcher.Invoke((Action)this.InitializeStyles);
		}

		private void InitializeStyles()
		{
			Style tableHeaderStyle = new Style();
			tableHeaderStyle.Setters.Add(new Setter(TableRow.FontWeightProperty, FontWeights.Bold));
			tableHeaderStyle.Setters.Add(new Setter(TableRow.BackgroundProperty, Brushes.Silver));
			this.TableHeaderRowStyle = tableHeaderStyle;
		}

		public Style TableHeaderRowStyle { get; private set; }
		public FlowDocument Document { get; private set; }

		private Paragraph _currentParagraph;
		protected Paragraph CurrentParagraph
		{
			get
			{
				this.EnsureCurrentParagraph();
				return this._currentParagraph;
			}
		}

		public void NewParagraph()
		{
			this._currentParagraph = null;
		}

		private void CreateParagraph()
		{
			this._currentParagraph = new Paragraph();

			if (this._tableCell != null)
				this._tableCell.Blocks.Add(this._currentParagraph);
			else
				this.Document.Blocks.Add(this._currentParagraph);
		}
		private void EnsureCurrentParagraph()
		{
			if (this._currentParagraph != null) return;
			this.Dispatcher.Invoke((Action)this.CreateParagraph);
		}

		#region Text
		public void AppendElement(Inline element)
		{
			if (element == null) throw new ArgumentNullException("element");
			this.Dispatcher.Invoke((Action<Inline>)this.CurrentParagraph.Inlines.Add, element);
		}
		public void WriteStyledText(string text, Style style)
		{
			Run run = (Run)this.Dispatcher.Invoke((Func<string, Style, Run>)CreateStyledTextRun, text, style);
			this.AppendElement(run);
		}
		private Run CreateStyledTextRun(string text, Style style)
		{
			if (text == null) throw new ArgumentNullException("text");
			Run run = new Run(text) { Style = style };
			return run;
		}
		#endregion
		#region Hyperlinks
		private Hyperlink CreateHyperlink(string text, Style textStyle, Style hyperlinkStyle)
		{
			Run innerRun = this.CreateStyledTextRun(text, textStyle);
			Hyperlink link = new Hyperlink(innerRun)
			{
				Style = hyperlinkStyle,
				// UNDONE: Causes error when copying flow document text containing the link to the clipboard
				//Command = this.GoToMemberCommand,
				//CommandParameter = member
			};

			return link;
		}
		public void WriteHyperlink(
			string text,
			Style textStyle,
			Style hyperlinkStyle,
			ICommand command,
			object commandArgument
			)
		{
			if (text == null) throw new ArgumentNullException("text");
			if (command == null) throw new ArgumentNullException("command");

			Hyperlink link = (Hyperlink)this.Dispatcher.Invoke((Func<string, Style, Style, Hyperlink>)this.CreateHyperlink, text, textStyle, hyperlinkStyle);
			// Setting Hyperlink.Command causes a problem when trying to copy text to clipboard
			// Instead, handle it using events
			link.Click += CreateClickCommandEventHandler(command, commandArgument);
			this.AppendElement(link);
		}

		private RoutedEventHandler CreateClickCommandEventHandler(ICommand command, object arg)
		{
			return (object sender, RoutedEventArgs e) =>
			{
				command.Execute(arg);
				e.Handled = true;
			};
		}
		#endregion

		#region Line breaks
		private void AppendLineBreakImpl()
		{
			this.AppendElement(new LineBreak());
		}
		public void WriteLineBreak()
		{
			this.Dispatcher.Invoke((Action)this.AppendLineBreakImpl);
		}
		#endregion

		#region Tables
		private Table _table;
		private TableRowGroup _tableRowGroup;
		private TableRow _tableRow;
		private TableCell _tableCell;
		private bool _isTableHeader;

		private void StartTableImpl()
		{
			this._isTableHeader = true;
			this._table = new Table();
			this._tableRowGroup = new TableRowGroup();
			this._table.RowGroups.Add(this._tableRowGroup);
			this.Document.Blocks.Add(this._table);
			this.NewRowImpl();
		}
		public void StartTable()
		{
			if (this._table != null) throw new InvalidOperationException(Messages.CannotStartTableMessage);

			this.Dispatcher.Invoke((Action)this.StartTableImpl);
		}

		private void NewRowImpl()
		{
			this._tableRow = new TableRow();
			if (this._isTableHeader)
			{
				this._tableRow.Style = this.TableHeaderRowStyle;
			}
			this._tableRowGroup.Rows.Add(this._tableRow);
			this.NewCellImpl();
			this._isTableHeader = false;

		}
		public void NewRow()
		{
			this.Dispatcher.Invoke((Action)this.NewRowImpl);
		}
		private void NewCellImpl()
		{
			this._tableCell = new TableCell();
			this._tableRow.Cells.Add(this._tableCell);
			this.NewParagraph();
		}
		public void NewCell()
		{
			this.Dispatcher.Invoke((Action)this.NewCellImpl);
		}
		public void EndTable()
		{
			this._table = null;
			this._tableRow = null;
			this._tableRowGroup = null;
			this._tableCell = null;

			this.NewParagraph();
		}
		#endregion
	}
}
